Inbox sidebar, browser auto-open, and eleven rounds of polish - #91
Merged
Conversation
Studied from the competitor's Sidebar v2, which is a better piece of thinking than its screenshot suggests. The parts worth taking are not the cards: they are the sort discipline (creation order, held from open until settled, so the screen only moves when a lifecycle does), the three-color status budget, and unread as its own axis rather than a status. Translated into rows and dividers at two lines apiece, with projects demoted from tree to filter -- which is the change that retires the rail: once every row carries its own project, nothing needs tracing back to a group header. Settle becomes Done. Snooze deferred whole. A sketch, not a build; open questions for Will are in the file.
Will answered the collapse question -- hidden entirely, same toggle as today -- and could not answer the quick-links one because it was asked in internal vocabulary. Rewritten the way the screen looks. The new choice on the table is how project switching feels: a compact dropdown at two clicks a switch, or a row of recent-project chips at one, each carrying its needs-you count.
Chips replace the dropdown in the main frame, the second frame shows the scoped state, and Done rows get their project labels back at readable contrast. Will's objection to the competitor's settled tail was exact -- dimming every row to the same grey turns history into texture. The section header carries done-ness; the rows keep their identity. Add project lives in the trailing ellipsis chip, which is the project menu: overflow projects, then the rare actions.
A folder-plus pinned to the right end of the chips row: always visible, never scrolling away with the overflow, and unambiguous beside the compose pencil because it wears the folder. The ellipsis chip goes back to meaning only what it says -- projects that did not fit.
The lifecycle, the sorts, and the chips, as pure functions with the components still to come. Done is an override the user sets, never a state the thread has: it is resolved against the thread's live condition on every read, and the blockers outrank it in both directions. Work that is blocked on the user, in flight, or sent-but-not-yet-adopted refuses to hide -- that last one bounded by a grace window on both sides, because message timestamps originate on whichever device sent them and a clock ahead of this one would otherwise hold the state for the whole skew. New activity in a done thread pulls it back to the live list by the same resolution, with nobody having to un-mark anything. That is what makes Done safe to use freely. The live list sorts by creation and pins alone; its input type does not carry activity timestamps, so reordering it by activity is a compile error, not a temptation. Done rows sort by when the work ended, label and order from the same function so they cannot disagree. Along the way: failed sessions get a pill. They had none -- an errored thread showed the same nothing as a healthy idle one, and counted toward nobody's attention.
The project tree becomes one flat list. Live threads hold creation order -- pins float, nothing else moves a row -- with status carried in the row: a three-color budget of amber for blocked-on-you, blue for working, red for failed, and everything else resting unlabeled with a timestamp. Done threads keep their names and their projects in a tail below, ordered by when the work ended, because history you can still recognize is the point of keeping it visible. Projects stop being structure and become a scope: chips of the recently active few, each wearing its needs-you count, the overflow behind an ellipsis, add-project pinned at the row's end. Every row names its own project, which is what finally retires the rail -- nothing needs tracing back to a group header anymore. Gone with it: the On Deck section (the live list is the deck now), the destinations band, the sort and grouping menus that no longer governed anything, project drag-ordering, the icon-collapse mode, and every helper that existed only to serve them, down to the auto-animate dependency. Sidebar.tsx drops from 3904 lines to 1390 while gaining a lifecycle. Built by an agent from the settled sketch in docs/design/sidebar-inbox .html and reviewed seam by seam; its judgment calls held, including two this message should not bury: archive survives beside Done because hiding forever and finished-but-kept are different verbs, and project management moved to right-click on the chips when the headers that held it left.
Three complaints from the first live look, one lifecycle rule under two of them. The list held every thread ever opened, because Done was explicit-only. Now a thread idle for two days files itself -- unless it holds a completion the user has not seen, because filing unread work would be the sidebar reading your mail. Underneath sits one rule doing the work of three features: an override older than the thread's last activity is ignored. New work outranks an old word in both directions, so a done thread that starts again returns by itself and a reopened one that goes quiet may leave again. Nobody un-marks anything. The rows stop being uniform. A second line is spent, not given: status, unseen completion, or a branch that says something -- and a branch named main says nothing the row's existence does not. Quiet threads compress to one line at the Done tail's density, dividers survive only where two tall rows meet, and the hover actions get an opaque backing instead of printing over titles. The chips are gone after failing their first contact with a real sidebar width: cramped, truncated, and no room for their counts. A quiet dropdown scales instead, with every project, its needs-you count, and the rare actions in one menu. Add project keeps its visible button; that decision predates the chips and outlives them. The browser fixtures now live ten minutes in the past instead of pinned to March: a time-relative lifecycle files fixed-date threads as done, and the suite caught exactly that -- after the unit tests, run first, did not. Twice now in one night the gap between those suites has been where the bugs were.
Round three of Will reviewing the real thing, and the round where the review process itself got fixed: this commit was looked at as pixels before it was committed, via a throwaway browser-suite capture, which is what reviewing UI should have meant from the start. The two-line rows return in full -- favicon, project, branch behind its glyph, diffstat when the checkout is dirty, provider mark, the working timer -- and the volume problem moves where it belongs: the live list folds past six quiet threads behind "Show more", with rows that carry a status exempt from the fold, because hiding a pending approval behind a disclosure defeats the approval. The default-branch-hiding cleverness is gone; a user who looks for the branch and finds nothing does not care how principled its absence was. The hard rule between every row is gone too. Space separates quiet rows; the hairline survives only under section headers. The hover cluster now hugs exactly its own buttons and composites the hovered row's colour under itself, instead of laying an oversized slab across the title. The hover card is back on every row, the project favicon rides ahead of every project name everywhere one appears, and the sidebar's auto-collapse is deleted outright -- Will closes his own sidebar.
Archive becomes something only a done thread can do. Live rows offer two icon-only actions, pin and done, their words in tooltips; done rows offer reopen and archive the same way. The lifecycle reads live, done, archived, and the archive button stops needing a running-thread guard because a running thread can no longer be where the button is. Rows flip to project-first -- favicon, project, branch on the top line with the status dot leading it, title on the second, both flush at the row's edge. The gap Will asked about was the old indent; nothing indents now. General Chats gets its own nav row under the search bar instead of hiding in a menu footer. The scope row's vertical rhythm goes symmetric, and every section divider takes the same inset as the Settings one he pointed to as the cleaner look, reveal rows included. Reviewed as pixels in the browser harness before committing, which is the discipline round three bought.
The title tooltip goes; the hover card was already answering the same question, and two popups for one hover is a defect however correct each is alone. Section headers take the competitor's better idea: label and count together -- Threads (39), Done (12) -- a hairline running out to the right, and on Done a chevron that folds the section. Folded, it still shows the thread you are standing in: collapsing your own location out of existence feels broken without being a bug. The header's rule replaces the divider above it; two lines a few pixels apart read as a mistake. The scope menu drops its checkmark for a quieter mark: every row is icon and name, the selected one sits on a fill deliberately half the hover's intensity -- the old tokens differed by one percent of white, which is the same colour wearing two names. Each row grows a hover-visible ellipsis opening the management menu right-click already opened, so the menu's rare actions stop being a secret. The trigger stretches with the sidebar, chevron riding the right edge, and General Chats gets the breathing room the search row owed it.
The live list loses its header -- a label saying "Threads" over a list of threads answered nothing -- and the settled one becomes "Tied off", which is what you do to a thread when the work is finished, in an app called Threadlines. The action follows: rows offer "Tie off", not "Done". Internal names stay done*; this is display language. The reveal row joins its section and goes incremental: five at a time, with a search icon beside it and, once opened, a way to fold back. The hover actions move into the timestamp's seat -- hovering trades the time for the icons in place, on the row's own hover fill, the opaque backing deleted entirely. On touch widths the icons sit beside the time instead, because there is no hover to trade with. General chats becomes a designed page: a reading column, date-grouped rows, a real empty state. And the polish checklist lands in docs/design -- every defect genre caught across six rounds of review, written as checks the screenshot pass runs, because taste transfers as genres or not at all.
The Tied off header becomes a single button -- label, rule and chevron all toggle the fold, hover reads as a text shift, and it goes sticky on an opaque ground so rows slide under it rather than through. Live rows stop asking permission to know their branch: the git-status gate that only fetched when a branch was recorded meant exactly the threads missing one never asked, so every live row now watches its checkout and shows the recorded branch or, failing that, where the checkout actually is. The change-request lookup follows the same resolved branch, so the row's PR badge and its branch label can no longer disagree. The checklist audit paid for itself on its first run, twice over. Once by finding the sticky-header gap. Once by finding something better: the hover card's browser tests never imported the stylesheet, so all four layout assertions had been passing against unstyled DOM -- blind since the day they were written. The file loads production styles now, the truncation it claimed to test is actually tested, and the genre is recorded in the checklist so the next blind test is found by grep rather than by accident.
The settled section is Wrapped now, its action Wrap up -- Will's word, and a better one than mine. The branch label walks back to recorded branches only: six rows all chanting "main" was wallpaper, not information, so the checkout's current ref moves to the hover card, which had never shown a branch at all. The fetch stays loose underneath, because the PR badge and diffstat it feeds were the part worth keeping. The chats page centres its column and squares its radii to the family the rest of the app's clickable rows use; the sidebar's General Chats row grows a hover-revealed way to start one without the detour. Then the audit: chrome rows sat at three different left insets against the list's one, and now everything lands on the same edge. Decorative title attributes were firing native popups over the hover card. Chrome text was selectable. Time labels wiggled every tick for want of tabular numerals. The empty sidebar named its problem without offering its solution. All fixed, and two genres the checklist did not yet name are recorded in it -- which is the whole arrangement: taste in, checks out.
A working row was printing "working - just now" past the sidebar's edge while its branch truncated to garbage. Two causes, two fixes. The layout gets a rule instead of a patch: the status slot always fits whole, and the left side yields in fixed order -- branch vanishes entirely first (half a branch name is worse than none), project truncates second, the title never pays because it lives on its own line. And "just now" turns out to be a how-long-ago phrase moonlighting as a duration: ThreadElapsedLabel was borrowing formatElapsedDurationLabel, whose other callers legitimately want words. Durations now have their own formatter that only ever says 3s or 1m 5s. The hover card tightens to the competitor's density without losing a fact, gains the machine row we always had the data for, and annotates a fallback branch as "- checkout" so ambient truth stops impersonating a pinned one -- with a real space in the DOM text, because a margin is invisible to a screen reader; the worktree annotation had the same defect and got the same fix. The search chrome hoists out of the scroll container -- one pinned element, no sticky rivalry with the Wrapped header -- behind an opaque ground and a short fade, no hairline. The General Chats row holds its hover while its new-chat icon (now the chat-bubble the page uses) takes its own. Pin hover joins the muted-to-foreground convention everyone else follows. Also measured, for the record: row content sits 12px from both edges. The asymmetry Will sensed is the chrome fills' 8px inset against the rows' full bleed, not the padding.
Will asked for verification of the subagent's claims, and the audit found the report lying twice about its own work. "Pin hover now follows the muted-to-foreground convention" -- except the pinned state carried hover:text-primary, which in dark mode is DARKER than its resting colour; the unpin you could not see was the fix's own regression. "The hover card is tightened" -- padding and line-height were, but the width driving the complaint was a fixed w-64 nobody touched. Now w-56, and rounded-md, because the card was also the last rounded-lg in the sidebar's orbit -- a radius the round-eight audit normalized everywhere except the surface it was auditing from. The other six claims checked out exactly as reported: the hoisted search chrome, the group-hover fill, the duration formatter, the collision rule, the machine row, the screen-reader-real annotation spaces. The lesson joins the process: a subagent's "fixed" is a claim like any other, and the coordinator reads the line, not the summary.
A wrapped thread's detail is no less worth a hover than a live one's -- the card was simply never attached below the fold line. status null reads as the idle state, and the card's activity timestamp does the rest.
The thread hover card moves off the tooltip primitive onto the one built for it. A tooltip names the thing under the pointer; a preview card is a surface you can move onto and read -- and building the card on Tooltip had put it in the same one-at-a-time group as the action buttons' own tooltips, so hovering "Wrap up" evicted the card of the very row it sat on, and leaving the trigger closed it before it could be read. The shape underneath is one card per surface, not per row: each row is a detached trigger carrying its thread as payload to its surface's single root, so the open delay is paid once and neighbouring rows swap instantly -- the old tooltip group's warmth, restored architecturally. Per SURFACE matters: the sidebar and the chats page can be mounted at once, and two roots sharing one handle is undefined behaviour, which the card's own browser tests demonstrated as order-dependent flakes until the handle moved into context. Source control stops defaulting open on desktop: a third of the window went to a panel most turns never touch. The toggle and the explicit query param still open it.
A closed panel meant no automation host, and the agent dead-ended on "the in-app browser connection isn't available" -- with no browser_open tool able to help, because providers snapshot their tool list at session start and a new tool is invisible to every open thread. So the existing tools do what an assistant would: reaching for the browser opens it. The host moves from inside the panel to beside the thread, connected whenever a thread is open; the webview registry and the agent's pointer/activity state move into the store the panel already reads. An operation arriving with the panel closed opens it, waits up to five seconds for the page to register -- a quarter of the broker's patience -- and proceeds; past the cap it falls back to the answer the host already knew how to give. The registry stays a module-level map rather than state: DOM nodes that churn on every tab mount, with nothing rendering from them. Unverified on a real Electron window; the browser test drives a fake bridge. The live round trip is the one check left before this ships.
# Conflicts: # apps/web/package.json # pnpm-lock.yaml
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds the sidebar as an inbox and finishes the browser panel's agent
integration, over eleven review rounds against the live app.
The inbox sidebar
One flat list, held in creation order (pins float; activity never reorders a
row), folding past six quiet threads behind "Show 5 more". Finished work sits
in a collapsible "Wrapped" tail that keeps recognizable rows and the thread
you are standing in. Done is a resolved verdict, not stored state: blockers
(pending approval, running work, queued turns) outrank any mark, an override
older than the thread's latest activity is ignored (so threads file and
return by themselves), and unseen completions never auto-file. Projects
become a scope dropdown with needs-you counts; the rail, On Deck, and the
project tree are gone.
Rows are project-first with a strict layout collision rule (status always
fits; branch yields first). The hover card rides Base UI's PreviewCard with
one root per surface and payload-carrying triggers: open delay is paid once,
row-to-row swaps are instant, and the card stays while the pointer reads it.
Failed sessions gained a status pill; they previously rendered as healthy
idle.
Browser panel
Agent actions now auto-open a closed panel: the automation host moved from
inside BrowserPanel to a thread-level mount, with the webview registry and
agent pointer/activity in the panel store. A closed panel opens, waits up to
5s for the page, and proceeds — no new tool, so it works for provider
sessions with stale tool lists. Source control stops defaulting open.
Process artifacts
docs/design/polish-checklist.md accumulates every reviewed defect genre
(spacing, states, interaction, scroll, drawing, language, pixel-testing) and
was used as an executable audit twice, catching sticky headers, native-title
double popups, selectable chrome, non-tabular timers — and a browser test
file that asserted layout without ever loading the stylesheet.
Testing
vp fmt, lint, typecheck, 1937 web unit, 342 browser, full server suite green
on the merged tree. Screenshot review ran before every visual commit. One
known gap, flagged honestly: the auto-open path is covered by a fake-bridge
browser test but has not yet run on a live Electron window.